package besta.moon.metadata;

import java.sql.*;

import besta.moon.sql.Expression;
import besta.moon.sql.parser.SQLTokenizer;
import besta.moon.MoonException;
import besta.moon.MoonCode;
import besta.moon.sql.expression.Expressions;

public class MoonResultSetMetaData implements ResultSetMetaData {

    public Expressions columns;

    @Override
    public int getColumnCount() throws SQLException {
        return columns.size();
    }

    @Override
    public boolean isAutoIncrement(int column) throws SQLException {
        return getColumnExpression(column).isAutoIncrement();
    }

    @Override
    public boolean isCaseSensitive(int column) throws SQLException {
        return getColumnExpression(column).isCaseSensitive();
    }

    @Override
    public boolean isSearchable(int column) throws SQLException {
        int type = getColumnExpression(column).getType();
        return type == Expression.NAME || type == Expression.FUNCTION;
    }

    @Override
    public boolean isCurrency(int column) throws SQLException {
        switch (getColumnExpression(column).getDataType()) {
            case SQLTokenizer.MONEY:
            case SQLTokenizer.SMALLMONEY:
                return true;
        }
        return false;
    }

    @Override
    public int isNullable(int column) throws SQLException {
        return getColumnExpression(column).isNullable() ? columnNullable
                : columnNoNulls;
    }

    @Override
    public boolean isSigned(int column) throws SQLException {
        return isSignedDataType(getColumnExpression(column).getDataType());
    }

    static boolean isSignedDataType(int dataType) {
        switch (dataType) {
            case SQLTokenizer.SMALLINT, SQLTokenizer.INT, SQLTokenizer.BIGINT, SQLTokenizer.SMALLMONEY, SQLTokenizer.MONEY, SQLTokenizer.DECIMAL, SQLTokenizer.NUMERIC, SQLTokenizer.REAL, SQLTokenizer.FLOAT, SQLTokenizer.DOUBLE -> {
                return true;
            }
        }
        return false;
    }

    public static boolean isNumberDataType(int dataType) {
        return isSignedDataType(dataType) || dataType == SQLTokenizer.TINYINT;
    }

    public static boolean isBinaryDataType(int dataType) {
        switch (dataType) {
            case SQLTokenizer.BINARY, SQLTokenizer.VARBINARY, SQLTokenizer.LONGVARBINARY, SQLTokenizer.BLOB -> {
                return true;
            }
        }
        return false;
    }

    public static int getDisplaySize(int dataType, int precision, int scale) {
        return switch (dataType) {
            case SQLTokenizer.BIT -> 1;
            case SQLTokenizer.BOOLEAN -> 5;
            case SQLTokenizer.TINYINT -> 3;
            case SQLTokenizer.SMALLINT -> 6;
            case SQLTokenizer.INT -> 10;
            case SQLTokenizer.BIGINT, SQLTokenizer.MONEY -> 19;
            case SQLTokenizer.REAL -> 13;
            case SQLTokenizer.FLOAT, SQLTokenizer.DOUBLE -> 17;
            case SQLTokenizer.LONGVARCHAR, SQLTokenizer.LONGNVARCHAR, SQLTokenizer.LONGVARBINARY, SQLTokenizer.JAVA_OBJECT, SQLTokenizer.BLOB, SQLTokenizer.CLOB, SQLTokenizer.NCLOB -> Integer.MAX_VALUE;
            case SQLTokenizer.NUMERIC -> precision + (scale > 0 ? 2 : 1);
            case SQLTokenizer.VARBINARY, SQLTokenizer.BINARY -> 2 + precision * 2;
            case SQLTokenizer.SMALLDATETIME -> 21;
            default -> precision;
        };
    }

    public static int getDataTypePrecision(int dataType, int defaultValue) {
        switch (dataType) {
            case SQLTokenizer.NULL -> {
                return 0;
            }
            case SQLTokenizer.BIT, SQLTokenizer.BOOLEAN -> {
                return 1;
            }
            case SQLTokenizer.TINYINT -> {
                return 3;
            }
            case SQLTokenizer.SMALLINT -> {
                return 5;
            }
            case SQLTokenizer.INT, SQLTokenizer.SMALLMONEY -> {
                return 10;
            }
            case SQLTokenizer.BIGINT, SQLTokenizer.MONEY -> {
                return 19;
            }
            case SQLTokenizer.REAL -> {
                return 7;
            }
            case SQLTokenizer.FLOAT, SQLTokenizer.DOUBLE -> {
                return 15;
            }
            case SQLTokenizer.CHAR, SQLTokenizer.NCHAR, SQLTokenizer.VARCHAR, SQLTokenizer.NVARCHAR, SQLTokenizer.BINARY, SQLTokenizer.VARBINARY -> {
                if (defaultValue == -1) {
                    return 0xFFFF;
                }
                return defaultValue;
            }
            case SQLTokenizer.NUMERIC, SQLTokenizer.DECIMAL -> {
                if (defaultValue == -1) {
                    return 38;
                }
                return defaultValue;
            }
            case SQLTokenizer.TIMESTAMP -> {
                return 23;
            }
            case SQLTokenizer.TIME -> {
                return 8;
            }
            case SQLTokenizer.DATE -> {
                return 10;
            }
            case SQLTokenizer.SMALLDATETIME -> {
                return 16;
            }
            case SQLTokenizer.UNIQUEIDENTIFIER -> {
                return 36;
            }
            case SQLTokenizer.LONGVARCHAR, SQLTokenizer.LONGNVARCHAR, SQLTokenizer.LONGVARBINARY -> {
                return Integer.MAX_VALUE;
            }
        }
        if (defaultValue == -1) {
            throw new Error("Precision:" + SQLTokenizer.getKeyWord(dataType));
        }
        return defaultValue;
    }

    @Override
    public int getColumnDisplaySize(int column) throws SQLException {
        return getColumnExpression(column).getDisplaySize();
    }

    @Override
    public String getColumnLabel(int column) throws SQLException {
        return getColumnExpression(column).getAlias();
    }

    @Override
    public String getColumnName(int column) throws SQLException {
        return getColumnExpression(column).getAlias();
    }

    @Override
    public String getSchemaName(int column) throws SQLException {
        return null;
    }

    @Override
    public int getPrecision(int column) throws SQLException {
        return getColumnExpression(column).getPrecision();
    }

    @Override
    public int getScale(int column) throws SQLException {
        return getColumnExpression(column).getScale();
    }

    @Override
    public String getTableName(int column) throws SQLException {
        return getColumnExpression(column).getTableName();
    }

    @Override
    public String getCatalogName(int column) throws SQLException {
        return null;
    }

    @Override
    public int getColumnType(int column) throws SQLException {
        return SQLTokenizer.getSQLDataType(getColumnExpression(column)
                .getDataType());
    }

    @Override
    public String getColumnTypeName(int column) throws SQLException {
        return SQLTokenizer.getKeyWord(getColumnExpression(column)
                .getDataType());
    }

    @Override
    public boolean isReadOnly(int column) throws SQLException {
        return !getColumnExpression(column).isDefinitelyWritable();
    }

    @Override
    public boolean isWritable(int column) throws SQLException {
        return getColumnExpression(column).isDefinitelyWritable();
    }

    @Override
    public boolean isDefinitelyWritable(int column) throws SQLException {
        return getColumnExpression(column).isDefinitelyWritable();
    }

    @Override
    public String getColumnClassName(int column) throws SQLException {
        return switch (getColumnType(column)) {
            case Types.TINYINT, Types.SMALLINT, Types.INTEGER -> "java.lang.Integer";
            case Types.BIT, Types.BOOLEAN -> "java.lang.Boolean";
            case Types.BINARY, Types.VARBINARY, Types.LONGVARBINARY -> "[B";
            case Types.BLOB -> "java.sql.Blob";
            case Types.BIGINT -> "java.lang.Long";
            case Types.DECIMAL, Types.NUMERIC -> "java.math.BigDecimal";
            case Types.REAL -> "java.lang.Float";
            case Types.FLOAT, Types.DOUBLE -> "java.lang.Double";
            case Types.DATE -> "java.sql.Date";
            case Types.TIME -> "java.sql.Time";
            case Types.TIMESTAMP -> "java.sql.Timestamp";
            case Types.CHAR, Types.VARCHAR, Types.LONGVARCHAR, -11 -> "java.lang.String";
            case Types.CLOB -> "java.sql.Clob";
            default -> "java.lang.Object";
        };
    }

    public final int getColumnIdx(int column) throws SQLException {
        if (column < 1 || column > columns.size()) {
            throw MoonException.create(MoonCode.COL_IDX_OUT_RANGE,
                    String.valueOf(column));
        }
        return column - 1;
    }

    public final Expression getColumnExpression(int column) throws SQLException {
        return columns.get(getColumnIdx(column));
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        
        return null;
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
  
        return false;
    }

}
